import { FocusMonitor } from '@angular/cdk/a11y';
import { BACKSPACE, DOWN_ARROW, UP_ARROW } from '@angular/cdk/keycodes';
import {
  AfterViewInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ElementRef,
  EventEmitter,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  Output,
  Renderer2,
  SimpleChanges,
  ViewChild,
  ViewEncapsulation,
  forwardRef,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { isNil as _isNil } from 'lodash';
import { InputBoolean, InputNumber, LvSizeLD } from '../core/api';

@Component({
  selector: 'lv-spinner',
  exportAs: 'lvSearch',
  templateUrl: './spinner.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef((): typeof SpinnerComponent => SpinnerComponent),
      multi: true,
    },
  ],
  host: {
    '[class.lv-spinner]': 'true',
    '[class.lv-spinner-focus]': 'focused',
    '[class.lv-spinner-disabled]': 'disabled',
    '[class.lv-spinner-inputnumber]': 'onlyInput',
    '[class.lv-spinner-size-large]': 'size === "large"',
  },
})
export class SpinnerComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy, ControlValueAccessor {
  // 最小值,number = -Math.pow(2, 53)
  @Input('lvMin') @InputNumber() min: number;
  // 最大值,number = Math.pow(2, 53)
  @Input('lvMax') @InputNumber() max: number;
  // 输入框大小
  @Input('lvSize') size: LvSizeLD = 'default';
  // 步长
  @Input('lvStep') @InputNumber() step: number = 1;
  // 输入框默认文字
  @Input('lvPlaceHolder') placeholder: string = '';
  // tslint:disable-next-line
  @Output() onBlur: EventEmitter<any> = new EventEmitter();
  // tslint:disable-next-line
  @Output() onFocus: EventEmitter<any> = new EventEmitter();
  // tslint:disable-next-line
  @Output() onChange: EventEmitter<any> = new EventEmitter();
  // 是否不能为空。如果为true，内容清空时会自动变成ngModel初始化的值（默认值）
  @Input('lvRequired') @InputBoolean() required: boolean = true;

  // 禁用状态
  @Input('lvDisabled') @InputBoolean() disabled: boolean = false;

  @Input('lvReadOnly') @InputBoolean() readonly: boolean = false;
  // 是否自动聚焦
  @Input('lvAutoFocus') @InputBoolean() autoFocus: boolean = false;
  // 是否为数字输入框（无按钮）
  @Input('lvOnlyInput') @InputBoolean() onlyInput: boolean = false;

  @ViewChild('input', { static: false }) inputElement: ElementRef<any>;

  public value: any;
  // 是否获得焦点
  public focused: boolean;
  // 显示值
  public displayValue: any;
  // 数值精度，为0即为整数
  public precision: number = 0;
  // 增加按钮禁用状态
  public plusDisabled: boolean = false;
  // 减少按钮禁用状态
  public lessDisabled: boolean = false;
  // 自动调整数值定时器
  private autoAdjustTimer;
  // 默认值
  private _defaultValue: number | string;
  // 最大值边界
  private Maximum: number = Number.MAX_SAFE_INTEGER;
  // 最小值边界
  private Minimum: number = Number.MIN_SAFE_INTEGER;
  // 是否拼音输入中
  private isInComposition: boolean = false;

  constructor(
    private elementRef: ElementRef,
    private renderer: Renderer2,
    private cdr: ChangeDetectorRef,
    private focusMonitor: FocusMonitor,
  ) {}

  registerOnChange(fn: any): void {
    this._onModelChange = fn;
  }

  registerOnTouched(fn: any): void {
    this._onModelTouch = fn;
  }

  ngAfterViewInit(): void {
    if (this.autoFocus) {
      this.focus();
    }
  }

  focus(): void {
    this.focusMonitor.focusVia(this.inputElement, 'keyboard');
  }

  /**
   * 判断最大最小值是否超过边界
   */
  isOutOfBoundary(val): boolean {
    return val < this.Minimum || val > this.Maximum;
  }

  /**
   * 是否为undefined 或 null
   */
  isNotNil(value: any): boolean {
    return typeof value !== 'undefined' && value !== null;
  }

  ngOnDestroy(): void {
    this.focusMonitor.stopMonitoring(this.elementRef);
    this.stop();
  }

  /**
   * 清除定时器
   */
  stop(): void {
    if (this.autoAdjustTimer) {
      clearTimeout(this.autoAdjustTimer);
    }
  }

  onCompositionstart(): void {
    this.isInComposition = true;
  }

  onCompositionend(e: CompositionEvent, value: string, target: any): void {
    this.setCompositionValue(value, target, e.data);
    this.isInComposition = false;
  }

  /**
   * 计算值的精准度
   */
  toAccurate(val: number, precision: number): string {
    const power = Math.pow(10, precision);
    return String(Math.round(val * power) / power);
  }

  /**
   * 传入最大最小值的合法性校验
   */
  checkMinAndMax(val, type?): number {
    let res = val;
    if (isNaN(Number(val)) || !this.isNotNil(val)) {
      res = type === 'max' ? this.Maximum : this.Minimum;
    }
    return res;
  }

  /**
   * 设置有效值，失焦校验
   */
  setValidValue(): void {
    const value = this.checkValue(this.value);
    this.value = this.setMaxMin(value);
    this.inputElement.nativeElement.value = this.isNotNil(this.value) ? this.value : '';
    this._onModelChange(this.value);
  }

  writeValue(value: any): void {
    this.value = this.checkValue(value);
    if (this.required && this.isNotNil(this.value)) {
      this._defaultValue = this.value;
    }
    this.buttonDisableStatus(this.value);
    this.cdr.markForCheck();
  }

  /**
   * 传入值的合法性校验
   */
  checkValue(val): number {
    const reg = /^-?[0-9]+(\.[0-9]+)?$/;

    if (this.isNotNil(val) && val !== '') {
      if (reg.test(val)) {
        return Number(val);
      } else {
        return this.min ? this.min : 0;
      }
    } else {
      return null;
    }
  }

  setCompositionValue(value: string, target: any, data: string): void {
    if (!data) {
      return;
    }
    const start = target.selectionStart;
    // target.selectionStart在value赋值后会更改，故先保存
    target.value = value.replace(data, '');
    const newStart = start - data.length;
    target.setSelectionRange(newStart, newStart);
  }

  /**
   * 判断按钮是否可用
   */
  buttonDisableStatus(val): void {
    if (!this.isNotNil(val)) {
      this.plusDisabled = false;
      this.lessDisabled = false;
      return;
    }

    this.plusDisabled = val >= this.max;
    this.lessDisabled = val <= this.min;

    if (this.plusDisabled || this.lessDisabled) {
      this.stop();
    }
    this.cdr.markForCheck();
  }

  validateInput(val: string): boolean {
    if (!val) {
      return true;
    }
    const firstChar = val[0];
    if (!/^[0-9-.]$/.test(firstChar)) {
      return false;
    }
    if (firstChar === '-') {
      const rest = val.slice(1);
      if (!rest) {
        return true;
      }
      return /^[0-9]+\.?[0-9]*$/.test(rest);
    }
    return /^[0-9]*\.?[0-9]*$/.test(val);
  }

  /**
   * 上下按键微调
   */
  onKeyDown(event: KeyboardEvent): void {
    // tslint:disable-next-line:deprecation
    const keyCode = event.charCode || event.keyCode || event.which;

    switch (keyCode) {
      case UP_ARROW:
        this.add(event);
        this.stop();
        break;
      case DOWN_ARROW:
        this.reduce(event);
        this.stop();
        break;
      default:
        break;
    }
  }

  /**
   * 过滤输入值
   * 精度为0时，只能输入数字；精度不为0，可以输入数字和小数点
   */
  onKeyPress(event: KeyboardEvent): void {
    // tslint:disable-next-line:deprecation
    const keyCode = event.charCode || event.keyCode || event.which;
    const reg = this.precision === 0 ? /[0-9-]/ : /[0-9.-]/;
    const inputVal = String.fromCharCode(keyCode);

    switch (keyCode) {
      case BACKSPACE:
        break;
      default:
        if (!reg.test(inputVal)) {
          event.preventDefault();
        }
    }
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.max) {
      this.max = this.checkMinAndMax(changes.max.currentValue, 'max');
      this.buttonDisableStatus(this.value);
    }

    if (changes.min) {
      this.min = this.checkMinAndMax(changes.min.currentValue);
      this.buttonDisableStatus(this.value);
    }

    if (changes.disabled) {
      this.disabled = changes.disabled.currentValue;
      this.cdr.markForCheck();
    }
  }

  setMaxMin(val): any {
    let value = val;
    if (_isNil(value)) {
      if (this.required && this.isNotNil(this._defaultValue)) {
        value = this._defaultValue;
        Promise.resolve().then((): void => this.onChange.emit(value));
      }
    } else if (value > this.max) {
      value = this.max;
      Promise.resolve().then((): void => this.onChange.emit(value));
    } else if (value < this.min) {
      value = this.min;
      Promise.resolve().then((): void => this.onChange.emit(value));
    } else if (value > this.Maximum) {
      value = this.Maximum;
      Promise.resolve().then((): void => this.onChange.emit(value));
    } else if (value < this.Minimum) {
      value = this.Minimum;
      Promise.resolve().then((): void => this.onChange.emit(value));
    }

    return value;
  }

  ngOnInit(): void {
    // 根据步长计算数值精度,得到小数的位数
    if (this.step % 1 !== 0) {
      this.precision = this.step.toString().split(/[,]|[.]/)[1].length;
    }

    this.focusMonitor.monitor(this.elementRef, true).subscribe((origin): void => {
      if (origin) {
        this.focused = true;
        this.onFocus.emit();
      } else {
        this.focused = false;
        this.setValidValue();
        this.buttonDisableStatus(this.value);
        this.onBlur.emit(this.value);
        Promise.resolve().then((): any => this._onModelTouch());
      }
    });
  }

  /**
   * 获取区间[min, max]内绝对值最小的值
   */
  getValueWithTheSmallestAbsoluteValueInTheRange(min: number, max: number): number {
    let value = 0;
    const right = isNaN(Number(max)) || !this.isNotNil(max) ? this.Maximum : max;
    const left = isNaN(Number(min)) || !this.isNotNil(min) ? this.Minimum : min;

    if (right >= 0) {
      value = Math.max(left, 0);
    } else {
      value = right;
    }

    return value;
  }

  /**
   * 递增
   */
  add(event: MouseEvent | KeyboardEvent): void {
    if (this.disabled || this.plusDisabled) {
      return;
    }
    if (!this.focused) {
      this.focus();
    }
    /**
     * 如果value值为null undefined ''，点加/减， 将value设置为可取范围内绝对值最小的值；如果最大/最小值没有，设置为0
     */
    if (!this.isNotNil(this.value) || this.value === '') {
      const value = this.getValueWithTheSmallestAbsoluteValueInTheRange(this.min, this.max);
      this.value = value - Number(this.step);
    }

    this.adjustmentValue('add', event);
  }

  /**
   * 递减
   */
  reduce(event: MouseEvent | KeyboardEvent): void {
    if (this.disabled || this.lessDisabled) {
      return;
    }
    if (!this.focused) {
      this.focus();
    }
    /**
     * 如果value值为null undefined ''，点加/减， 将value设置为可取范围内绝对值最小的值；如果最大/最小值没有，设置为0
     */
    if (!this.isNotNil(this.value) || this.value === '') {
      const value = this.getValueWithTheSmallestAbsoluteValueInTheRange(this.min, this.max);
      this.value = value + Number(this.step);
    }
    this.adjustmentValue('reduce', event);
  }

  /**
   * 获取合法值
   */
  getValidValue(val: string): any {
    let value: number;

    const regZero = /^-?[0-9]+(\.0+)?$/; // 匹配类似x.0 x.00 x.000以及对应的负数等的输入
    const reg = /^-?0+[1-9]+(\.0+)?$/; // 匹配类似00 00.00以及对应的负数等的输入
    if (this.precision && regZero.test(val) && !reg.test(val)) {
      return val;
    }

    if (val.trim() === '') {
      value = null;
    } else {
      // 输入的值不合理，直接返回输入值，外部进行校验
      const reg2 = /^-?[0-9]+(\.[0-9]+)?$/;
      const isValid = reg2.test(val) && val.split('.').length <= 2;

      if (isValid) {
        value = this.precision ? parseFloat(val) : parseInt(val, 10);
      } else {
        value = NaN;
      }
    }

    return isNaN(value) ? val : value;
  }

  onInput(e: InputEvent, value: string, target: any): void {
    this.stop();

    // 中文输入过程中，需要等待中文输入结束后（即onCompositionend），再去对输入结果做处理。
    if (this.isInComposition) {
      return;
    }

    // 粘贴时只有全部为数字的才显示
    if (e.inputType === 'insertFromPaste' && isNaN(Number(value))) {
      target.value = '';
    } else if (e.inputType !== 'insertCompositionText' && isNaN(Number(e.data)) && !['.', '-'].includes(e.data)) {
      /**
       * 非360浏览器中文输入时
       * 输入会触发一次input，enter键后还会触发一次input,且赋值后会导致12a变成1。具体可参考issue513
       * 故非360浏览器使用onCompositionend处理中文输入，360浏览器不支持onCompositionend
       * ['.', '-'] 小数点和减号可正常输入
       */
      this.setCompositionValue(value, target, e.data);
    } else if (!this.validateInput(value)) {
      // input内容校验回退为默认值
      target.value = this.value;
    }

    this.value = target.value;
    this.buttonDisableStatus(this.value);
    this._onModelChange(this.value);
    this.onChange.emit(this.value);
  }

  /**
   * 调整数值
   */
  adjustmentValue(type: string, event: MouseEvent | KeyboardEvent): void {
    this.stop();
    event.preventDefault();

    if (this.disabled) {
      return;
    }

    let currentValue: number; // 被修改值
    let step; // 基数
    let temporaryValue: number; // 临时值
    const min = this.isOutOfBoundary(this.checkMinAndMax(this.min)) ? this.Minimum : this.checkMinAndMax(this.min);
    const max = this.isOutOfBoundary(this.checkMinAndMax(this.max, 'max'))
      ? this.Maximum
      : this.checkMinAndMax(this.max, 'max');

    if (type === 'add') {
      step = Number(this.step);
    } else {
      step = this.step * -1;
    }

    if (this.value) {
      currentValue = typeof this.value === 'string' ? this.getValidValue(this.value) : this.value;
    } else {
      currentValue = 0;
    }

    if (this.precision) {
      const power = Math.pow(10, this.precision);
      const accurateValue = ((currentValue * power + step * power) / power).toFixed(this.precision);
      temporaryValue = parseFloat(accurateValue);
    } else {
      temporaryValue = parseInt(currentValue + step, 10);
    }

    if (temporaryValue < min) {
      temporaryValue = min;
    } else if (temporaryValue > max) {
      temporaryValue = max;
    }

    this.value = temporaryValue;
    this.focused = true;

    this.autoAdjustTimer = setTimeout((): void => {
      this.adjustmentValue(type, event);
    }, 500);

    this.buttonDisableStatus(this.value);
    this._onModelChange(this.value);
    this.onChange.emit(this.value);
  }

  private _onModelTouch: any = (): void => {};

  // 实现NG_VALUE_ACCESSOR接口
  private _onModelChange: any = (): void => {};
}
